/************************************************************************
 * @file AbstractPlugin.h
 * @author Ian Chen
 * @version 1.0
 *
 * @section LICENSE
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
 * General Public License for more details at
 * http://www.gnu.org/licenses/lgpl.html
 *
 ************************************************************************/
#ifndef ABSTRACTPLUGIN_H
#define ABSTRACTPLUGIN_H

#include <QObject>
#include <QTranslator>

#include <plugin/plugin_global.h>
#include <plugin/command/AbstractCommand.h>
#include <plugin/plugin/PluginContext.h>
#include <plugin/plugin/PluginDescription.h>
#include <plugin/service/AbstractService.h>
#include <plugin/service/ServiceRegistry.h>
#include <plugin/util/StatusEventListener.h>

namespace antu
{
namespace plugin
{
/**
 * @brief This is the core of the plugin system. All components of the system
 *        starts with plugin instance.
 *
 * All modules are wrapped inside a plugin, and hence AbstractPlugin should be
 * the base class for all modules. A plugin can retrieve needed services from
 * other plugins, provide services to other plugins, and to startup initialization.
 */
class PLUGINSHARED_EXPORT AbstractPlugin : public QObject
{
    Q_OBJECT

protected:
    /**
     * @brief Default constructor. This should not be used by plugins other than
     *        FrameworkPlugin.
     */
    AbstractPlugin();

public:
    /**
     * @brief Constructor for plugins.
     * @param pDescription Pointer to plugin description.
     * @param pServiceRegistry Pointer to service registry that holds all services provided
     *                         by plugins.
     */
    AbstractPlugin(PluginDescriptionSharedPtr pDescription, ServiceRegistry* pServiceRegistry);

    /**
     * @brief Destructor
     */
    virtual ~AbstractPlugin();

    /**
     * @brief Initializes a plugin.
     * @param pContext Plugin context object.
     *
     * After all needed plugins are instantiated by PluginActivator, the activator
     * then initializes plugins in three steps: initialize(), start(), and finishStartup().
     *
     * Plugins that need to do initialization should override doInit(), doStart() and
     * doFinishStartup() respectively to complete necessary logic handling during plugin
     * initialization.
     *
     * Note that every PluginActivator will do instantiation and initialization for all
     * its needed plugins. Therefore these methods including the plugin constructor may
     * not be called just once.
     */
    void initialize(PluginContext* pContext);

    /**
     * @brief Starts the plugin.
     */
    void start();

    /**
     * @brief When all plugins are instantiated, initialized and started, finishStartup()
     *        is called to do startup finishup steps.
     */
    void finishStartup();

    /**
     * @brief Called when a plugin is stopped. Any termination handling should be put
     *        in sub-class's doStop() method.
     *
     * Note that this again may not be a once in an application deal for a plugin, as
     * within the application, multiple PluginActivators maybe used.
     */
    void stop();

    /**
     * @brief A descriptive name of the plugin.
     * @return Name of the plugin.
     */
    inline QString name() const
    {
        return this->description()->name();
    }

    /**
     * @brief Code of the plugin. This is used to uniquely identify a plugin within
     *        the plugin system. Make sure plugins have unique codes.
     * @return Code of the plugin.
     */
    inline QString code() const
    {
        return this->description()->code();
    }

    /**
     * @brief Start level of the plugin. Plugins that have a smaller start level value
     *        will be started earlier than plugins that have a larger start level value.
     * @return Start level of the plugin.
     *
     * During plugin instantiation and initialization stage, plugin description files
     * are first parsed. Plugins that do not need other plugins will be started first,
     * and within this group, plugins that have smaller start level values will be started
     * sooner than plugins that have larger start level values. For plugin startup
     * logic, see PluginDependencyResolver document.
     */
    inline int startLevel() const
    {
        return this->description()->startLevel();
    }

    /**
     * @brief Executes a command provided by this plugin.
     * @param commandLine Command line text used as input to command
     * @param outputs Output of the plugin command.
     * @return Return value indication command execution success or the reason of failure.
     */
    AbstractCommand::CommandReturnType execute(const QString& commandLine, CommandParameters& outputs);

    /**
     * @brief Executes a command provided by this plugin.
     * @param serviceName Name of the service that provides the command.
     * @param commandName Name of the command that is of interest.
     * @param inputs Inputs to the command.
     * @param outputs Output of the plugin command.
     * @return Return value indication command execution success or the reason of failure.
     */
    AbstractCommand::CommandReturnType execute(const QString& serviceName,
                                               const QString& commandName,
                                               const CommandParameters& inputs,
                                               CommandParameters& outputs);

    /**
     * @brief Plugin context object.
     * @return Pointer to plugin context.
     */
    inline PluginContext* context() const
    {
        return this->m_pPluginContext;
    }

    /**
     * @brief Plugin description
     * @return Shared pointer to plugin description.
     */
    inline PluginDescriptionSharedPtr description() const
    {
        return this->m_pPluginDescription;
    }

protected:
    /**
     * @brief Invoked after a plugin is instantiated, and before it is
     *        initialized.
     * @param pContext Plugin context.
     */
    virtual void doPreInit(PluginContext* pContext);

    /**
     * @brief Registers services provided by plugin to service registry.
     * @param pContext
     */
    void doRegisterService(PluginContext* pContext);

    /**
     * @brief Plugin initialization.
     * @param pContext Plugin context object.
     */
    virtual void doInit(PluginContext* pContext) = 0;

    /**
     * @brief Plugin startup.
     */
    virtual void doStart() = 0;

    /**
     * @brief Plugin clean up.
     */
    virtual void doStop() = 0;

    /**
     * @brief Plugin startup tidying. After all plugins are started,
     *        this method is called for all started plugins to do finish up work.
     */
    virtual void doFinishStartup() = 0;

    /**
     * @brief A required service is found, need to override this to keep track of required service.
     * @param pService pointer to required service.
     */
    virtual void requiredServiceFound(AbstractService* pService);

    /**
     * @brief Convenient method to report startup progress.
     * @param pListener Listener to output progress. This maybe a progress bar, a splash screen or just console.
     * @param progress Percentage of completion.
     * @param statusText Status text.
     */
    inline void reportProgress(StatusEventListener* pListener, int progress, const QString& statusText)
    {
        if (pListener != nullptr)
        {
            pListener->setStatusText(statusText, progress);
        }
    }

public:
    /**
     * @brief Convenient method to log debug information.
     * @param file Name of the file that generated the log message.
     * @param line Line number of the file that generated the log message.
     * @param className Name of the class that generated the log message.
     * @param methodName Name of the method that generated the log message.
     * @param message Actual log message.
     */
    inline void debug(const QString& file, int line, const QString& className, const QString& methodName,
        const QString& message) const;

    /**
     * @brief Convenient method to log  information.
     * @param file Name of the file that generated the log message.
     * @param line Line number of the file that generated the log message.
     * @param className Name of the class that generated the log message.
     * @param methodName Name of the method that generated the log message.
     * @param message Actual log message.
     */
    inline void info(const QString& file, int line, const QString& className, const QString& methodName,
        const QString& message) const;

    /**
     * @brief Convenient method to log warning information.
     * @param file Name of the file that generated the log message.
     * @param line Line number of the file that generated the log message.
     * @param className Name of the class that generated the log message.
     * @param methodName Name of the method that generated the log message.
     * @param message Actual log message.
     */
    inline void warning(const QString& file, int line, const QString& className, const QString& methodName,
        const QString& message) const;

    /**
     * @brief Convenient method to log error information.
     * @param file Name of the file that generated the log message.
     * @param line Line number of the file that generated the log message.
     * @param className Name of the class that generated the log message.
     * @param methodName Name of the method that generated the log message.
     * @param message Actual log message.
     */
    inline void error(const QString& file, int line, const QString& className, const QString& methodName,
        const QString& message) const;

    /**
     * @brief Convenient method to log fatal information.
     * @param file Name of the file that generated the log message.
     * @param line Line number of the file that generated the log message.
     * @param className Name of the class that generated the log message.
     * @param methodName Name of the method that generated the log message.
     * @param message Actual log message.
     */
    inline void fatal(const QString& file, int line, const QString& className, const QString& methodName,
        const QString& message) const;

    /**
     * @brief Convenient method to allow plugin to load its translation files.
     * @param translationFile Name of the translation file, in absolute or relative path.
     *                        When relative path is used, position starts from language path
     *                        specified in plugin context.
     * @return
     */
    bool loadTranslationFile(const QString& translationFile);

public slots:
    /**
     * @brief Invoked when a service is registered to service registry.
     * @param pluginCode Code of the plugin.
     * @param serviceName Name of the service.
     * @param pService Pointer to the service object.
     */
    void serviceFound(const QString& pluginCode, const QString& serviceName, AbstractService* pService);

protected:
    PluginContext*             m_pPluginContext;
    PluginDescriptionSharedPtr m_pPluginDescription;
    ServiceRegistry*           m_pServiceRegistry;
    map<QString, AbstractService*> m_oServices;
    QTranslator m_oTranslator;
};

typedef shared_ptr<AbstractPlugin> AbstractPluginSharedPtr;
}
}

#endif // ABSTRACTPLUGIN_H
